package com.mo.common.lucene;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.mo.common.lucene.interfaceAction.LuceneReader;
import com.mo.common.lucene.utils.LuceneClient;
import com.mo.common.lucene.utils.LuceneUtils;

/**
 * public void queryParserTest() throws IOException, ParseException {
 * Analyzer analyzer = new StandardAnalyzer(); // 标准分词器，适用于英文
 * Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
 * Analyzer analyzer = new ComplexAnalyzer();//中文分词
 * Analyzer analyzer = new IKAnalyzer();//中文分词
 * created by harry on 2016/5/18.
 */
public class LuceneQueryManager {
//    private Logger logger = LoggerFactory.getLogger(LuceneQueryManager.class);
    private static LuceneQueryManager luceneQueryManager=null;  
    private static Map<String ,LuceneQueryManager> luceneQueryManagerMap = new HashMap<String ,LuceneQueryManager>();
    //静态工厂方法   
    public static LuceneQueryManager getInstance(String indexerName) {  
    	if(indexerName==null){
    		return null;
    	}
    	if(luceneQueryManagerMap.containsKey(indexerName)){
    		luceneQueryManager = luceneQueryManagerMap.get(indexerName);
    		if(luceneQueryManager==null){
    			luceneQueryManagerMap.put(indexerName, new LuceneQueryManager(new LuceneClient(indexerName)));
    		}
    	}else{
    		luceneQueryManagerMap.put(indexerName, new LuceneQueryManager(new LuceneClient(indexerName)));
    	}
    	return luceneQueryManagerMap.get(indexerName);  
    }
    private LuceneClient luceneClient = null;
	public LuceneQueryManager(LuceneClient luceneClient){
		this.luceneClient = luceneClient;
	}
	public LuceneQueryManager(){
		
	}

    /**
     * 多条件查询
     * <p>
     * BooleanQuery也是实际开发过程中经常使用的一种Query。
     * 它其实是一个组合的Query，在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。
     * BooleanQuery本身来讲是一个布尔子句的容器，它提供了专门的API方法往其中添加子句，
     * 并标明它们之间的关系，以下代码为BooleanQuery提供的用于添加子句的API接口：
	 * 
	 * @param reader
	 * @param map 多个参数的联合查询，
	 * @param mapBooleanClause 设置查询的类型必须
	 * mapBooleanClause 设置每个参数的检索类型没有的话为SHOULD
     * 1．MUST和MUST：取得连个查询子句的交集。
     * 2．MUST和MUST_NOT：表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。
     * 3．SHOULD与MUST_NOT：连用时，功能同MUST和MUST_NOT。
     * 4．SHOULD与MUST连用时，结果为MUST子句的检索结果,但是SHOULD可影响排序。
     * 5．SHOULD与SHOULD：表示“或”关系，最终检索结果为所有检索子句的并集。
     * 6．MUST_NOT和MUST_NOT：无意义，检索无结果。
	 * @return
	 * @throws Exception
	 */
    public <T> T BooleanQueryTest(LuceneReader<T> reader,Map<String, String> map,Map<String, String> mapBooleanClause) throws Exception {
    	BooleanQuery.Builder builder = new BooleanQuery.Builder();
    	for (Map.Entry<String, String> entry : map.entrySet()) {  
    		Query query = new TermQuery(new Term(entry.getKey(), entry.getValue()));
//    	    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); 
    		// BooleanClause用于表示布尔查询子句关系的类，
            // 包 括：
            // BooleanClause.Occur.MUST，
            // BooleanClause.Occur.MUST_NOT，
            // BooleanClause.Occur.SHOULD。
            // 必须包含,不能包含,可以包含三种.有以下6种组合：
    		if(mapBooleanClause.containsKey(entry.getKey())){
    	    	if(mapBooleanClause.get(entry.getKey()).equals("MUST")){
    	    		builder.add(query, BooleanClause.Occur.MUST);
    	    	}else if(mapBooleanClause.get(entry.getKey()).equals("MUST_NOT")){
    	    		builder.add(query, BooleanClause.Occur.MUST_NOT);
    	    	}else{
    	    		builder.add(query, BooleanClause.Occur.SHOULD);
    	    	}
    	    }else{
    	    	builder.add(query, BooleanClause.Occur.SHOULD);
    	    }
    	} 
        
        BooleanQuery query = builder.build();
        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }


    /**
     * 匹配前缀
     * <p>
     * PrefixQuery用于匹配其索引开始以指定的字符串的文档。就是文档中存在xxx%
     * <p>
     *
     * @throws IOException
     */
    public <T> T prefixQueryTest(LuceneReader<T> reader,String searchField, String prefixStr) throws Exception {
        Term term = new Term(searchField, prefixStr);
        Query query = new PrefixQuery(term);
        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }

    /**
     * 短语搜索
     * <p>
     * 所谓PhraseQuery，就是通过短语来检索，比如我想查“big car”这个短语，
     * 那么如果待匹配的document的指定项里包含了"big car"这个短语，
     * 这个document就算匹配成功。可如果待匹配的句子里包含的是“big black car”，
     * 那么就无法匹配成功了，如果也想让这个匹配，就需要设定slop，
     * 先给出slop的概念：slop是指两个项的位置之间允许的最大间隔距离
     *
     * @throws IOException
     */
    public <T> T phraseQueryTest(LuceneReader<T> reader,String searchField, List<String> userInputPhrase,int slop) throws Exception {
        PhraseQuery.Builder builder = new PhraseQuery.Builder();
        for(int i=0;i<userInputPhrase.size();i++){
        	builder.add(new Term(searchField, userInputPhrase.get(i)));
        }
        builder.setSlop(slop);
        PhraseQuery query = builder.build();

        //执行查询，并打印查询到的记录数
//        return executeQuery(reader, phraseQuery);
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }

    /**
     * 相近词语搜索
     * <p>
     * FuzzyQuery是一种模糊查询，它可以简单地识别两个相近的词语。
     *
     * @throws IOException
     */
    public <T> T fuzzyQueryTest(LuceneReader<T> reader,String searchField, String userInputStr) throws Exception {
        Term t = new Term(searchField, userInputStr);
        Query query = new FuzzyQuery(t);
        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }

    /**
     * 通配符搜索
     * <p>
     * Lucene也提供了通配符的查询，这就是WildcardQuery。
     * 通配符“?”代表1个字符，而“*”则代表0至多个字符。
     *
     * @throws IOException
     */
    public <T> T queryTermParserTest(LuceneReader<T> reader,String searchField, String userInputParserStr) throws Exception {
//        String searchField = "content";
        Term term = new Term(searchField, userInputParserStr);//"大*规模");
        Query query = new WildcardQuery(term);

        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }


    /**
     * 该方法只适合类型为text的字段，对于string字段里的值是不能实现分词查找的
     * 
     * 分词查询
     *
     * @throws IOException
     * @throws ParseException
     */
    public <T> T queryParserTest(LuceneReader<T> reader,String searchField, String userInputStr) throws Exception {
        Analyzer analyzer = new IKAnalyzer();//中文分词
//        String searchField = "content";

        //指定搜索字段和分析器
        QueryParser parser = new QueryParser(searchField, analyzer);

        //用户输入内容
        Query query = parser.parse(userInputStr);//"计算引擎");

        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }

	/**
     * 多个 Field 分词查询(一个词在多个字段中去匹配)
     *
     * @throws IOException
     * @throws ParseException
     */
	 public <T> T multiFieldQueryParser(LuceneReader<T> reader,String[] filedStrArr, String userInputStr) throws Exception {
		Analyzer analyzer = new IKAnalyzer();//中文分词
//        String[] filedStr = new String[]{"title", "content"};
        //指定搜索字段和分析器
        QueryParser queryParser = new MultiFieldQueryParser(filedStrArr, analyzer);
        //用户输入内容
        Query query = queryParser.parse(userInputStr);
        //执行查询，并打印查询到的记录数
        return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }
	
	 /**
     * 按词条搜索
     * <p>
     * TermQuery是最简单、也是最常用的Query。TermQuery可以理解成为“词条搜索”，
     * 在搜索引擎中最基本的搜索就是在索引中搜索某一词条，而TermQuery就是用来完成这项工作的。
     * 在Lucene中词条是最基本的搜索单位，从本质上来讲一个词条其实就是一个名/值对。
     * 只不过这个“名”是字段名，而“值”则表示字段中所包含的某个关键字。
     *
	 * 精准过滤查找
	 * @param reader
	 * @param query
	 * @return
	 * @throws Exception
	 */
    public <T> T readForTermQuery(LuceneReader<T> reader,String searchField, String userInputParserStr) throws Exception {
    	TermQuery query = new TermQuery(new Term(searchField, userInputParserStr));
    	return LuceneUtils.executeQuery(this.luceneClient.getIndexReader(),reader, query);
    }
    
   
}
